---
title: 里氏替换原则
description: Liskov Substitution Principle (LSP) - 子类型必须能够替换其基类型
sidebar_position: 4
---

## 什么是里氏替换原则

里氏替换原则(Liskov Substitution Principle, LSP)是由 Barbara Liskov 在1986年提出的一个重要的面向对象设计原则。其核心思想是：

> 如果 S 是 T 的子类型，那么在不改变程序正确性的前提下，任何 T 类型的对象都可以被 S 类型的对象所替换。

简单来说，子类对象能够替换程序中父类对象出现的任何地方，并且保证原来程序的逻辑行为不变及正确性不被破坏。

## Design By Contract（按照协议设计）

里氏替换原则还有一个更具操作性的解释，即"Design By Contract"（按照协议设计）。这意味着：

1. 子类必须完全遵守父类的行为约定
2. 子类可以改变函数的内部实现逻辑，但不能改变函数原有的行为约定
3. 行为约定包括：
   - 函数声明要实现的功能
   - 对输入、输出、异常的约定
   - 注释中所罗列的任何特殊说明

## 实例说明

### 符合LSP的示例

```java
public class Transporter {
    protected HttpClient httpClient;
    
    public Transporter(HttpClient httpClient) {
        this.httpClient = httpClient;
    }
    
    public Response sendRequest(Request request) {
        // 使用 httpClient 发送请求
        return httpClient.send(request);
    }
}

public class SecurityTransporter extends Transporter {
    private String appId;
    private String appToken;
    
    public SecurityTransporter(HttpClient httpClient, String appId, String appToken) {
        super(httpClient);
        this.appId = appId;
        this.appToken = appToken;
    }
    
    @Override
    public Response sendRequest(Request request) {
        if (appId != null && appToken != null) {
            request.addPayload("app-id", appId);
            request.addPayload("app-token", appToken);
        }
        return super.sendRequest(request);
    }
}
```

在这个例子中，`SecurityTransporter` 扩展了 `Transporter` 的功能，但保持了原有的行为约定。

## 违反LSP的常见情况

### 1. 违背父类声明的功能

```java
public class Parent {
    public List<Order> sortOrdersByAmount(List<Order> orders) {
        // 按金额排序
        orders.sort((a, b) -> Double.compare(a.getAmount(), b.getAmount()));
        return orders;
    }
}

public class Child extends Parent {
    @Override
    public List<Order> sortOrdersByAmount(List<Order> orders) {
        // 违反LSP：改变了函数的功能，按日期而不是金额排序
        orders.sort((a, b) -> a.getCreateTime().compareTo(b.getCreateTime()));
        return orders;
    }
}
```

### 2. 违背输入输出约定

```java
public class Parent {
    public String process(int data) {
        // 约定：出错返回null
        try {
            return processData(data);
        } catch (Exception e) {
            return null;
        }
    }
}

public class Child extends Parent {
    @Override
    public String process(int data) {
        // 违反LSP：出错时抛出异常而不是返回null
        if (data < 0) {
            throw new IllegalArgumentException("Invalid data");
        }
        return processData(data);
    }
}
```

### 3. 违背注释说明

```java
public class Account {
    protected double balance;

    /**
     * 提现操作
     * 注释说明：提现金额不得超过账户余额
     */
    public boolean withdraw(double amount) {
        if (amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }
}

public class VIPAccount extends Account {
    @Override
    public boolean withdraw(double amount) {
        // 违反LSP：违背了注释中关于不得超过余额的说明
        balance -= amount; // 允许透支
        return true;
    }
}
```

## LSP与多态的区别

虽然LSP和多态看起来相似，但它们的关注点不同：

1. **多态**是面向对象编程的一种特性，是一种代码实现的思路
2. **里氏替换原则**是一种设计原则，用于指导如何合理地设计继承关系

## 实践建议

1. 在设计继承关系时，仔细思考父类的行为约定
2. 子类可以扩展功能，但不要改变父类原有的行为约定
3. 可以用父类的单元测试来验证子类是否符合LSP
4. 如果发现难以遵守LSP，可能需要重新思考继承关系的设计

## 总结

里氏替换原则的核心是"按照协议设计"，它要求：

1. 子类必须完全遵守父类的行为约定
2. 子类可以扩展功能，但不能改变原有的行为约定
3. 这个原则有助于保持系统的稳定性和可维护性
4. 违反LSP通常意味着继承关系可能需要重新设计
